package com.zmj.sy.mom.srv.aps.utils.ado;

import com.zmj.sy.mom.srv.aps.bean.bo.ApsWeekDay;
import com.zmj.sy.mom.srv.aps.bean.bo.ApsWeekDayWrap;
import com.zmj.sy.mom.srv.aps.bean.bo.ado.*;
import com.zmj.sy.mom.srv.aps.bean.entity.resource.Phase;
import com.zmj.sy.mom.srv.aps.mapper.AdoWorkDetailMapper;
import com.zmj.sy.mom.srv.aps.mapper.ApsDetailWeekdayMapper;
import com.zmj.sy.mom.srv.aps.utils.JsonUtils;
import com.zmj.sy.mom.srv.aps.utils.MkBeanUtils;
import com.zmj.sy.mom.srv.aps.utils.MkDateTimeUtils;
import com.zmj.sy.mom.srv.aps.utils.SyExceptionUtils;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;

public class AdoTaskUtils {

    public static AdoDetailWeekdayWrap scheduling(AdoFactoryCache factoryCache, AdoOrderCache orderCache, LocalDateTime startTime, AdoMachine machine, AdoTask currentTask, ApsDetailWeekdayMapper apsDetailWeekdayMapper, AdoWorkDetailMapper AdoWorkDetailMapper, List<AdoDetailWeekdayWrap> l, List<AdoMachine> machines, Map<String, Map<String, Integer>> phaseGroupWorkTimeMap) {

        // 计算 工位 并行能力


//        PhaseExt phaseExt = factoryCache.getPhaseExtMap().get(currentTask.getWorkDetail().getPhaseCode().replaceAll("\\d+$", ""));
//        TaskTimeUtils.calcTime(phase)

        Phase phase = factoryCache.getPhaseCodeMap().get(currentTask.getWorkDetail().getPhaseCode().replaceAll("\\d+$", ""));

        if(phase == null){
            throw SyExceptionUtils.e("PlmId：{}, 名称：{}, 工序未找到：{}", currentTask.getWorkDetail().getPlmId(), currentTask.getWorkDetail().getMaterName(), currentTask.getWorkDetail().getPhaseCode());
        }

        Integer allWorkTime = AdoTaskTimeUtils.calcTime(currentTask, phase, machine, machines, phaseGroupWorkTimeMap);

        // 找到包含并算出结束时间的日历
        AdoDetailWeekdayWrap temp = getStartCalendarWrapWuxian(factoryCache, startTime, allWorkTime, machine, apsDetailWeekdayMapper,AdoWorkDetailMapper);

        // 如果没有任务，随意放
        if(machine.getNextTask() == null){
            fullCalendarWrap(currentTask, temp,  machine, null);
            if(temp.getStartDateTime() == null || temp.getEndDateTime() == null){
                throw SyExceptionUtils.e("排产失败:" + JsonUtils.toJsonString(currentTask.getWorkDetail()));
            }
            currentTask.getWorkDetail().setTotalWorkTime(allWorkTime);
            currentTask.getWorkDetail().setEnterTime(machine.getStation().getEnterTime());
            currentTask.getWorkDetail().setOutTime(machine.getStation().getOutTime());
            currentTask.getWorkDetail().setChangTime(machine.getStation().getChangTime());
            return temp;
        }


        // 找到距离结束时间最近一个节点，准备往前排产
        AdoTask nextStep = getMachineTasks2(machine, temp.getEndDateTime());
        AdoStep prevStep = null;
        if (nextStep == null) {
            prevStep = getLastTask(machine);
            nextStep = machine.getNextTask();

            fullCalendarWrap(currentTask, temp,  prevStep, nextStep);

            if(temp.getStartDateTime() == null || temp.getEndDateTime() == null){
                throw SyExceptionUtils.e("排产失败:" + JsonUtils.toJsonString(currentTask.getWorkDetail()));
            }
            currentTask.getWorkDetail().setTotalWorkTime(allWorkTime);
            currentTask.getWorkDetail().setEnterTime(machine.getStation().getEnterTime());
            currentTask.getWorkDetail().setOutTime(machine.getStation().getOutTime());
            currentTask.getWorkDetail().setChangTime(machine.getStation().getChangTime());
            if(temp.getPrevious() == null){
                temp.setPrevious(machine);
            }
            return temp;
        }

        prevStep = nextStep.getPreviewTask();


        while (nextStep != null) {

            if(prevStep == null){
                if (temp.getEndDateTime().isAfter(nextStep.getWorkDetail().getPlanStartDateTime())) {
                    temp = getStartCalendarWrap(factoryCache, nextStep.getWorkDetail().getPlanStartDateTime(), allWorkTime, machine, apsDetailWeekdayMapper,AdoWorkDetailMapper);
                } else {
                    temp = getStartCalendarWrap(factoryCache, startTime, allWorkTime, machine, apsDetailWeekdayMapper,AdoWorkDetailMapper);
                }
                break;
            }

            if (temp.getEndDateTime().isAfter(nextStep.getWorkDetail().getPlanStartDateTime())) {
                temp = getStartCalendarWrap(factoryCache, nextStep.getWorkDetail().getPlanStartDateTime(), allWorkTime, machine, apsDetailWeekdayMapper,AdoWorkDetailMapper);
            }


            if(temp.getStartDateTime() == null || temp.getEndDateTime() == null || ((AdoTask)prevStep).getWorkDetail().getPlanStartDateTime() == null || ((AdoTask)prevStep).getWorkDetail().getPlanEndDateTime() == null){
                throw SyExceptionUtils.e("时间参数不能为空");
            }

            boolean overlappingAbutting = MkDateTimeUtils.isOverlappingAbutting(temp.getStartDateTime(), temp.getEndDateTime(), ((AdoTask)prevStep).getWorkDetail().getPlanStartDateTime(), ((AdoTask)prevStep).getWorkDetail().getPlanEndDateTime());
            if (overlappingAbutting) {
                break;
            }

            nextStep = (AdoTask) prevStep;
            prevStep = nextStep.getPreviewTask();
        }
        fullCalendarWrap(currentTask, temp, prevStep, nextStep);

        if(temp.getStartDateTime() == null || temp.getEndDateTime() == null){
            throw SyExceptionUtils.e("排产失败:" + JsonUtils.toJsonString(currentTask.getWorkDetail()));
        }
        currentTask.getWorkDetail().setTotalWorkTime(allWorkTime);
        currentTask.getWorkDetail().setEnterTime(machine.getStation().getEnterTime());
        currentTask.getWorkDetail().setOutTime(machine.getStation().getOutTime());
        currentTask.getWorkDetail().setChangTime(machine.getStation().getChangTime());
        if(temp.getPrevious() == null){
            temp.setPrevious(machine);
        }


        return temp;

    }

    public static AdoDetailWeekdayWrap getStartCalendarWrap(AdoFactoryCache factoryCache, LocalDateTime endTime, Integer taskAllWorkTime, AdoMachine machine, ApsDetailWeekdayMapper apsDetailWeekdayMapper, AdoWorkDetailMapper AdoWorkDetailMapper) {
        AdoDetailWeekdayWrap temp = new AdoDetailWeekdayWrap();
        for (int i = machine.getWeekDays().size() - 1; i >= 0 ; i--) {
            ApsWeekDay machineCalendar = machine.getWeekDays().get(i);

            if(endTime == null){
                return null;
            }

            // 如果开始时间在日历的开始时间和结束时间之间，则找到此日历
            if (!(endTime.isBefore(machineCalendar.getStartDateTime()) || endTime.isAfter(machineCalendar.getEndDateTime()))) {
                temp.setEndIndex(i);
                temp.getStartMachineCalendar().add(machineCalendar);
                temp.setEndDateTime(endTime);
                break;
            }

            if(endTime.isAfter(machineCalendar.getStartDateTime()) && endTime.isAfter(machineCalendar.getEndDateTime())){
                temp.setEndIndex(i);
                temp.getStartMachineCalendar().add(machineCalendar);
                temp.setEndDateTime(machineCalendar.getEndDateTime());
                break;
            }

            if (i == 0 && temp.getStartDateTime() == null) {
                temp.setEndIndex(i);
                temp.getStartMachineCalendar().add(machineCalendar);
                temp.setEndDateTime(endTime);
                break;
//                addFirstMachineCalendar(factoryCache ,machine, apsDetailWeekdayMapper,AdoWorkDetailMapper);
            }

        }

        long allWorkTime = taskAllWorkTime;

        if(allWorkTime == 0) {
            temp.setStartIndex(temp.getEndIndex());
            temp.setStartDateTime(temp.getEndDateTime());
            ApsWeekDay machineCalendar = machine.getWeekDays().get(temp.getEndIndex());
            ApsWeekDayWrap machineCalendarWrap = MkBeanUtils.copyProperties(machineCalendar, ApsWeekDayWrap.class);
            machineCalendarWrap.setSecond((int)allWorkTime);
            machineCalendarWrap.setStart(temp.getStartDateTime());
            machineCalendarWrap.setEnd(temp.getEndDateTime());
            temp.getAll().add(machineCalendarWrap);
            temp.setMachine(machine);
            return temp;
        }

        for (int i = temp.getEndIndex(); i >= 0; i--) {
            ApsWeekDay machineCalendar = machine.getWeekDays().get(i);
            LocalDateTime start = machineCalendar.getEndDateTime();
            if (i == temp.getEndIndex()) {
                start = temp.getEndDateTime();
            }
            LocalDateTime end = machineCalendar.getStartDateTime();
            long until = end.until(start, ChronoUnit.SECONDS);
            temp.getStartMachineCalendar().add(machineCalendar);
            if (allWorkTime <= until) {
                if (i == 0){
                    ApsWeekDayWrap machineCalendarWrap = MkBeanUtils.copyProperties(machineCalendar, ApsWeekDayWrap.class);
                    machineCalendarWrap.setSecond((int)until);
                    machineCalendarWrap.setStart(machineCalendar.getStartDateTime());
                    machineCalendarWrap.setEnd(start);
                    temp.setStartIndex(temp.getEndIndex());
                    temp.setStartDateTime(temp.getEndDateTime());
                    temp.getAll().add(machineCalendarWrap);
                    break;
                }

                temp.setStartIndex(i);
                temp.setStartDateTime(start.minusSeconds(allWorkTime));

                ApsWeekDayWrap machineCalendarWrap = MkBeanUtils.copyProperties(machineCalendar, ApsWeekDayWrap.class);
                machineCalendarWrap.setSecond((int)allWorkTime);
                machineCalendarWrap.setStart(temp.getStartDateTime());
                machineCalendarWrap.setEnd(start);
                temp.getAll().add(machineCalendarWrap);
                break;
            }
            allWorkTime -= until;
            if (i == 0 && temp.getStartDateTime() == null) {
//                addFirstMachineCalendar(factoryCache, machine, apsDetailWeekdayMapper,AdoWorkDetailMapper);
                ApsWeekDayWrap machineCalendarWrap = MkBeanUtils.copyProperties(machineCalendar, ApsWeekDayWrap.class);
                machineCalendarWrap.setSecond((int)until);
                machineCalendarWrap.setStart(machineCalendar.getStartDateTime());
                machineCalendarWrap.setEnd(start);
                temp.setStartIndex(temp.getEndIndex());
                temp.setStartDateTime(temp.getEndDateTime());
                temp.getAll().add(machineCalendarWrap);
                break;
            }

            ApsWeekDayWrap machineCalendarWrap = MkBeanUtils.copyProperties(machineCalendar, ApsWeekDayWrap.class);
            machineCalendarWrap.setSecond((int)until);
            machineCalendarWrap.setStart(start);
            machineCalendarWrap.setEnd(start.plusSeconds(until));
            temp.getAll().add(machineCalendarWrap);
        }

        temp.setMachine(machine);
//            temp.setStartDateTime(temp.getStartMachineCalendar().get(0).getStartDateTime());
//            temp.setEndDateTime(temp.getStartMachineCalendar().get(temp.getStartMachineCalendar().size() - 1).getEndDateTime());
        return temp;
    }

    public static AdoDetailWeekdayWrap getStartCalendarWrapWuxian(AdoFactoryCache factoryCache, LocalDateTime endTime, Integer taskAllWorkTime, AdoMachine machine, ApsDetailWeekdayMapper apsDetailWeekdayMapper, AdoWorkDetailMapper AdoWorkDetailMapper) {
        AdoDetailWeekdayWrap temp = new AdoDetailWeekdayWrap();
        for (int i = machine.getWeekDays().size() - 1; i >= 0 ; i--) {
            ApsWeekDay machineCalendar = machine.getWeekDays().get(i);

            if(endTime == null){
                return null;
            }

            // 如果开始时间在日历的开始时间和结束时间之间，则找到此日历
            if (!(endTime.isBefore(machineCalendar.getStartDateTime()) || endTime.isAfter(machineCalendar.getEndDateTime()))) {
                temp.setEndIndex(i);
                temp.getStartMachineCalendar().add(machineCalendar);
                temp.setEndDateTime(endTime);
                break;
            }

            if(endTime.isAfter(machineCalendar.getStartDateTime()) && endTime.isAfter(machineCalendar.getEndDateTime())){
                temp.setEndIndex(i);
                temp.getStartMachineCalendar().add(machineCalendar);
                temp.setEndDateTime(machineCalendar.getEndDateTime());
                break;
            }

            if (i == 0 && temp.getStartDateTime() == null) {
                temp.setEndIndex(i);
                temp.getStartMachineCalendar().add(machineCalendar);
                temp.setEndDateTime(endTime);
                break;
//                addFirstMachineCalendar(factoryCache ,machine, apsDetailWeekdayMapper,AdoWorkDetailMapper);
            }

        }

        long allWorkTime = taskAllWorkTime;

        if(allWorkTime == 0) {
            temp.setStartIndex(temp.getEndIndex());
            temp.setStartDateTime(temp.getEndDateTime());
            ApsWeekDay machineCalendar = machine.getWeekDays().get(temp.getEndIndex());
            ApsWeekDayWrap machineCalendarWrap = MkBeanUtils.copyProperties(machineCalendar, ApsWeekDayWrap.class);
            machineCalendarWrap.setSecond((int)allWorkTime);
            machineCalendarWrap.setStart(temp.getStartDateTime());
            machineCalendarWrap.setEnd(temp.getEndDateTime());
            temp.getAll().add(machineCalendarWrap);
            temp.setMachine(machine);
            return temp;
        }

        for (int i = temp.getEndIndex(); i >= 0; i--) {
            ApsWeekDay machineCalendar = machine.getWeekDays().get(i);
            LocalDateTime start = machineCalendar.getEndDateTime();
            if (i == temp.getEndIndex()) {
                start = temp.getEndDateTime();
            }
            LocalDateTime end = machineCalendar.getStartDateTime();
            long until = end.until(start, ChronoUnit.SECONDS);
            temp.getStartMachineCalendar().add(machineCalendar);
            if (allWorkTime <= until) {
                if (i == 0){
                    ApsWeekDayWrap machineCalendarWrap = MkBeanUtils.copyProperties(machineCalendar, ApsWeekDayWrap.class);
                    machineCalendarWrap.setSecond((int)until);
                    machineCalendarWrap.setStart(machineCalendar.getStartDateTime());
                    machineCalendarWrap.setEnd(start);
                    temp.setStartIndex(temp.getEndIndex());
                    temp.setStartDateTime(temp.getEndDateTime());
                    temp.getAll().add(machineCalendarWrap);
                    break;
                }

                temp.setStartIndex(i);
                temp.setStartDateTime(start.minusSeconds(allWorkTime));

                ApsWeekDayWrap machineCalendarWrap = MkBeanUtils.copyProperties(machineCalendar, ApsWeekDayWrap.class);
                machineCalendarWrap.setSecond((int)allWorkTime);
                machineCalendarWrap.setStart(temp.getStartDateTime());
                machineCalendarWrap.setEnd(start);
                temp.getAll().add(machineCalendarWrap);
                break;
            }
            allWorkTime -= until;
            if (i == 0 && temp.getStartDateTime() == null) {
//                addFirstMachineCalendar(factoryCache, machine, apsDetailWeekdayMapper,AdoWorkDetailMapper);
                ApsWeekDayWrap machineCalendarWrap = MkBeanUtils.copyProperties(machineCalendar, ApsWeekDayWrap.class);
                machineCalendarWrap.setSecond((int)until);
                machineCalendarWrap.setStart(machineCalendar.getStartDateTime());
                machineCalendarWrap.setEnd(start);
                temp.setStartIndex(temp.getEndIndex());
                temp.setStartDateTime(temp.getEndDateTime());
                temp.getAll().add(machineCalendarWrap);
                break;
            }

            ApsWeekDayWrap machineCalendarWrap = MkBeanUtils.copyProperties(machineCalendar, ApsWeekDayWrap.class);
            machineCalendarWrap.setSecond((int)until);
            machineCalendarWrap.setStart(start);
            machineCalendarWrap.setEnd(start.plusSeconds(until));
            temp.getAll().add(machineCalendarWrap);
        }

        temp.setMachine(machine);
//            temp.setStartDateTime(temp.getStartMachineCalendar().get(0).getStartDateTime());
//            temp.setEndDateTime(temp.getStartMachineCalendar().get(temp.getStartMachineCalendar().size() - 1).getEndDateTime());
        return temp;
    }

    public static void fullCalendarWrap(AdoTask currentTask, AdoDetailWeekdayWrap temp, AdoStep prevStep, AdoTask nextStep) {
        temp.setCurrentTask(currentTask);
        temp.setPrevious(prevStep);
        temp.setNext(nextStep);

    }

    public static AdoTask getMachineTasks2(AdoMachine machine, LocalDateTime dateTime) {
        List<AdoTask> tasks = new ArrayList<>();
        AdoTask temp = machine.getNextTask();
        AdoStep previewTask = null;
        while (temp != null) {
            tasks.add(temp);
            temp.setPreviewTask(previewTask);
            previewTask = temp;
            temp = temp.getNextTask();

        }

        Optional<AdoTask> max = tasks.stream().filter(e -> !e.getWorkDetail().getPlanEndDateTime().isBefore(dateTime)).min(Comparator.comparing(e->e.getWorkDetail().getPlanEndDateTime()));
        AdoTask prevStep = null;
        if (max.isPresent()) {
            prevStep = max.get();
        }

        return prevStep;
    }

    public static AdoTask getLastTask(AdoMachine machine) {
        if(machine.getNextTask() == null){
            return null;
        }
        AdoTask last = machine.getNextTask();
        while (last != null) {
            last = last.getNextTask();
        }
        return last;
    }

}
